package jp.co.nes.awssupport.business.logic;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.List;

import jp.co.nes.awssupport.business.utility.TargetAmountHelper;
import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.bean.ContractInformationBean;
import jp.co.nes.awssupport.common.bean.CreditCouponAssignedBean;
import jp.co.nes.awssupport.common.bean.CreditCouponBean;
import jp.co.nes.awssupport.common.bean.MonthlyCompiledBillingBean;
import jp.co.nes.awssupport.common.constant.CreditCouponHistoryKind;
import jp.co.nes.awssupport.common.utility.BeanUtil;
import jp.co.nes.awssupport.common.utility.DateUtil;

/**
 * クレジット処理です。
 */
public class CreditCouponProcessing {

    /** 処理対象年月 */
    private YearMonth targetYearMonth;

    /** 契約情報管理 */
    private ContractInformationManage ciManage;

    /**
     * 処理対象年月を引数にもつコンストラクタです。
     *
     * @param targetYearMonth 処理対象年月
     */
    public CreditCouponProcessing(YearMonth targetYearMonth) {
        this.targetYearMonth = targetYearMonth;
        ciManage = new ContractInformationManage(targetYearMonth);
    }

    /**
     * クレジットリストに含まれるクレジットを適用し、クレジット適用リストを返却します。<br>
     * なお、クレジット適用額(米ドル)は「ドル円 * クレジット適用額」で算出した結果を、小数第3位で四捨五入した値になります。<br>
     * 以下の場合、空リスト返却します。
     * <ul>
     * <li>クレジットリストがnull or 空リストの場合</li>
     * <li>クレジットリストに含まれるクレジットが全て使用されており、適用するものが存在しなかった場合</li>
     * </ul>
     * 【補足事項】
     * <ul>
     * <li>
     * 月次集計結果に含まれる請求額（税抜）は、小数第4位で四捨五入して小数第3位にした値の合計を、
     * 小数第1位で四捨五入して整数にした値である必要があります。
     * </li>
     * <li>クレジットリストは有効期限日内のクレジットだけが含まれている必要があります。</li>
     * <li>クレジットリストは有効期限日が古いクレジットから順番に含まれている必要があります。</li>
     * </ul>
     *
     * @param mcbBean 月次集計結果
     * @return クレジット適用リスト
     */
    public List<CreditCouponAssignedBean> apply(MonthlyCompiledBillingBean mcbBean) {
        String linkedAccountId = mcbBean.getLinkedAccountId();
        ContractInformationBean ciBean = ciManage.get(linkedAccountId);
        if (ciBean == null) {
            throw new CommonException("contract information not exist(linkedAccountId=" + linkedAccountId + ")");
        }

        List<CreditCouponAssignedBean> applyList = new ArrayList<>();
        List<CreditCouponBean> creditCouponList = ciBean.getCreditCouponList();
        BigDecimal dollerYen = ciBean.getExchangeRateBean().getDollerYen();

        if (creditCouponList == null || creditCouponList.isEmpty()) {
            return applyList;
        }

        long billingAmount = mcbBean.getBillingAmount().longValueExact();
        TargetAmountHelper helper = new TargetAmountHelper(linkedAccountId, targetYearMonth);
        long maxAssignedAmount = helper.getTargetAmountOfCc(billingAmount);
        long creditCouponAssignedTargetAmount = maxAssignedAmount;

        for (CreditCouponBean creditCouponBean : creditCouponList) {
            BigDecimal creditCouponBalanceUSDollarCalc = creditCouponBean.getCreditCouponBalanceUSDollarCalc();
            if (creditCouponBalanceUSDollarCalc == null) {
                continue;
            }

            long creditCouponBalance = getYen(creditCouponBalanceUSDollarCalc, dollerYen);
            if (creditCouponBalance <= 0) {
                // クレジット残高 <= 0の場合
                continue;
            }

            CreditCouponAssignedBean applyBean = new CreditCouponAssignedBean();
            applyBean.setLinkedAccountId(linkedAccountId);
            applyBean.setYearMonth(DateUtil.toInt(targetYearMonth));
            applyBean.setCreditCouponId(creditCouponBean.getCreditCouponId());
            applyBean.setTargetAmount(creditCouponAssignedTargetAmount);

            if (maxAssignedAmount <= 0) {
                YearMonth expirationYearMonth = DateUtil.toYearMonth(creditCouponBean.getExpirationDate());
                if (targetYearMonth.equals(expirationYearMonth)) {
                    // 処理対象年月と有効期限年月が同じ場合
                    // 無効にする額 = クレジット残高
                    long invalidAssignedAmount = creditCouponBalance;
                    CreditCouponAssignedBean invalidApplyBean = new CreditCouponAssignedBean();
                    BeanUtil.copyProperties(invalidApplyBean, applyBean);
                    invalidApplyBean.setTargetAmount(0L);
                    invalidApplyBean.setCreditCouponHistoryKind(CreditCouponHistoryKind.INVALID.toString());
                    invalidApplyBean.setCreditCouponAmount(invalidAssignedAmount);
                    invalidApplyBean.setCreditCouponAmountUSDollar(getUSDollar(invalidAssignedAmount, dollerYen));
                    applyList.add(invalidApplyBean);
                }
            } else {
                if (creditCouponBalance >= maxAssignedAmount) {
                    // クレジット残高 >= 最大適用額の場合
                    applyBean.setCreditCouponHistoryKind(CreditCouponHistoryKind.USE.toString());
                    applyBean.setCreditCouponAmount(maxAssignedAmount);
                    applyBean.setCreditCouponAmountUSDollar(getUSDollar(maxAssignedAmount, dollerYen));
                    applyList.add(applyBean);

                    YearMonth expirationYearMonth = DateUtil.toYearMonth(creditCouponBean.getExpirationDate());
                    if (targetYearMonth.equals(expirationYearMonth)) {
                        // 処理対象年月と有効期限年月が同じ場合
                        // 無効にする額 = クレジット残高 - 最大適用額
                        // なお「クレジット残高 >= 最大適用額の場合」なので、無効にする額は負にならない
                        // 適用額は、円もドルも「クレジット残高 - 最大適用額」で算出した値を使用する。
                        long invalidAssignedAmount = creditCouponBalance - maxAssignedAmount;
                        if (invalidAssignedAmount != 0) {
                            // 無効する額が0の場合は処理をしない
                            BigDecimal invalidAssignedAmountUSDollar = getUSDollar(creditCouponBalance, dollerYen)
                                    .subtract(getUSDollar(maxAssignedAmount, dollerYen));
                            CreditCouponAssignedBean invalidApplyBean = new CreditCouponAssignedBean();
                            BeanUtil.copyProperties(invalidApplyBean, applyBean);
                            invalidApplyBean.setTargetAmount(0L);
                            invalidApplyBean.setCreditCouponHistoryKind(CreditCouponHistoryKind.INVALID.toString());
                            invalidApplyBean.setCreditCouponAmount(invalidAssignedAmount);
                            invalidApplyBean.setCreditCouponAmountUSDollar(invalidAssignedAmountUSDollar);
                            applyList.add(invalidApplyBean);
                        }
                    }
                    maxAssignedAmount = 0;
                } else {
                    // クレジット残高 < 最大適用額の場合
                    applyBean.setCreditCouponHistoryKind(CreditCouponHistoryKind.USE.toString());
                    applyBean.setCreditCouponAmount(creditCouponBalance);
                    applyBean.setCreditCouponAmountUSDollar(getUSDollar(creditCouponBalance, dollerYen));
                    applyList.add(applyBean);

                    // 最大適用額 = 最大適用額 - クレジット残高
                    // なお「クレジット残高 < 最大適用額の場合」なので、最大適用額は負にならない
                    maxAssignedAmount = maxAssignedAmount - creditCouponBalance;
                }
            }
        }
        return applyList;
    }

    /**
     * 値を円に変換して返却します(小数第31位で四捨五入)。
     *
     * @param value 値
     * @param dollerYen ドル円
     * @return 円に変換した値
     */
    private long getYen(BigDecimal value, BigDecimal dollerYen) {
        // 値 * ドル円 (小数第1位で四捨五入)
        return value.multiply(dollerYen).setScale(0, RoundingMode.HALF_UP).longValueExact();
    }

    /**
     * 値をドルに変換して返却します(小数第3位で四捨五入)。
     *
     * @param value 値
     * @param dollerYen ドル円
     * @return ドルに変換した値
     */
    private BigDecimal getUSDollar(long value, BigDecimal dollerYen) {
        // 値 / ドル円 (小数第3位で四捨五入)
        BigDecimal valueOfBigDecimal = new BigDecimal(value);
        return valueOfBigDecimal.divide(dollerYen, 2, RoundingMode.HALF_UP);
    }
}
